iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Software Development

玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX系列 第 11 篇

lesson 2: Hello ASDF:CLISP的工程文件「Another System Definition Facility」

  • 分享至 

  • xImage
  •  

TL;DR

這篇會把前一篇的單一main.lisp拆成多個檔案,並用ASDF的系統定義檔案管理載入順序與依賴。完成後,可以透過asdf:load-system載入abcl-javafx,執行結果仍然是相同的HelloFX視窗。

雖然可以繼續直接將所有程式碼寫入一份文件,但為了後續的管理,最好還是使用ASDF將程式碼拆成個別檔案。

ASDF的全名是「Another System Definition Facility」。是類似Maven(JAVA)、package.json(Node.JS)部分功能的構建工具,申明系統依賴、套件依賴、檔案依賴。

建立 ASDF 系統定義

建立一個與系統同名的.asd檔案。以目前例子用abcl-javafx作爲系統名稱,建立abcl-javafx.asd檔案:

(defsystem "abcl-javafx"
  :description "Create GUI Application with ABCL and JavaFX"
  :version "0.0.1"
  :author "lagagain <lagagain@email.com>"
  :licence "MIT"
  :serial t ;; 申明需要依順序載入
  :components ((:file "load-lib")
               (:file "package")
               (:file "constants")
               (:file "main")))

除了最基本的系統名稱、描述、作者、版本外,最重要的是定義檔案載入的順序。雖然同樣可以在檔案中使用(load <file-path>),或是利用#'require、#'provide來定義檔案依賴,但使用ASDF是三者最簡單、容易管理的方式。在上面例子中使用:serial t申明必須依序載入,並且組成的檔案依序有:

  1. load-lib.lisp
  2. package.lisp
  3. constants.lisp
  4. main.lisp

到目前為止,這些檔案的副檔名都是.lisp,在ASDF申明中可以省略不寫。load-lib內容放在最後說明,所以首先是定義package:

定義 Package 與 JavaFX 類別

(defpackage :abcl-javafx
  (:use :cl :java))

在之後的檔案內容都會有(in-package :abcl-javafx),申明使用的Package空間。現在除了基本的:cl(common-lisp),主要先依賴載入:java的符號(symbol)。然後是定義常數constants:

(in-package :abcl-javafx)

(define-symbol-macro +application+ (jclass "javafx.application.Application"))
(define-symbol-macro +scene+ (jclass "javafx.scene.Scene"))
(define-symbol-macro +label+ (jclass "javafx.scene.control.Label"))
(define-symbol-macro +StackPane+ (jclass "javafx.scene.layout.StackPane"))
(define-symbol-macro +stage+ (jclass "javafx.stage.Stage"))

constants內容定義JAVA Class的別名,這裡不用更常使用的defconstant,是因為有些JavaFX類別需要延後載入,或是可以用JAVA內部類別名稱(簽名),其實也就是將#'jclass函式拿掉,不直接取得JAVA類別物件,直接定義成字串:

(in-package :abcl-javafx)

(defconstant +application+ "javafx.application.Application")
(defconstant +scene+ "javafx.scene.Scene")
(defconstant +label+ "javafx.scene.control.Label")
(defconstant +StackPane+ "javafx.scene.layout.StackPane")
(defconstant +stage+ "javafx.stage.Stage")

拆分 HelloFX 主程式

最後是主程式main:

(in-package :abcl-javafx)

(let* ((HelloFX (jnew-runtime-class "HelloFX"
      :superclass "javafx.application.Application"
      :methods `(("main" :void ()
                  (lambda (this)
                    (jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
                 ("start" :void ("javafx.stage.Stage")
                   (lambda (this stage)
                     (prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
                         (javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
                         (l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
                         (scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
                      (jcall "setScene" stage scene)
                      (jcall "show" stage)))))
      :access-flags '(:public))))
  (jcall "main" (jnew HelloFX)))

可以在把主類別的方法提取出來,會更清楚段落內容:


(in-package :abcl-javafx)

(defun HelloFX.main (this)
  (jcall "launch" this (jnew-array "java.lang.String" 1)))


;; 定義HelloFX.main函式
(defun HelloFX.main (this)
  (jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1)))

;; 定義HelloFX.start函式
(defun HelloFX.start (this stage)
  (prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
          (javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
          (l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
          (scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
     (jcall "setScene" stage scene)
     (jcall "show" stage)))

;; 簡化主程式內容
(let* ((HelloFX (jnew-runtime-class "HelloFX"
                                    :superclass +application+
                                    :methods '(("main" :void ()
                                                        HelloFX.main)
                                               ("start" :void ("javafx.stage.Stage")
                                                        HelloFX.start))
                                    :access-flags '(:public))))
  (jcall "main" (jnew HelloFX)))

載入 JavaFX JAR 檔案

最後是載入lib資料夾下的JAR檔案的部分load-lib:

(ignore-errors
  (let (libs)
    (setf libs
          (loop for file in (directory "lib/*.jar")
                do
                (setf file (namestring file))
                (print file)
                collect file))
    (add-to-classpath libs)))

不過其實ABCL有abcl-asdf擴充ASDF可用的定義內容,可以使用其中的jar-directory來取代load-lib,以下是調整後的abcl-javafx.asd內容:

(defsystem "abcl-javafx"
  :description "Create GUI Application with ABCL and JavaFX"
  :version "0.0.1"
  :author "lagagain <lagagain@email.com>"
  :licence "MIT"
  :defsystem-depends-on (abcl-asdf)   ;; 引入`abcl-asdf`
  :depends-on ()
  :serial t    ;; 申明需要依順序載入
  :components ((:jar-directory "lib")    ;; 直接使用`jar-directory`載入lib資料夾下的JAR檔案
               (:file "package")
               (:file "constants")
               (:file "main")))

或者可以在運行JAVA的時候,直接申明搜尋的CLASSPATH或Module路徑:

alias abcl='java --enable-native-access=javafx.graphics --module-path "${SDK_PATH}" --add-modules=javafx.controls,javafx.fxml,javafx.graphics --add-opens java.base/java.lang=ALL-UNNAMED -jar "${ABCL_PATH}"'

載入並執行 ASDF 系統

進到REPL模式後輸入:

(require :abcl-contrib)  ;; 如果沒有使用到`abcl-asdf`的話可以省略,只載入`(require :asdf)`
(push #P"./" asdf:*central-registry*)  ;; 將當前目錄加入system搜尋路徑
(asdf:load-system "abcl-javafx")

同樣可以將上述內容儲存成run-system.lisp,然後透過--load載入:

abcl --load run-system.lisp --eval '(quit)'

將上述檔案依照.asd中的順序放在同一個目錄後,執行這個指令即可載入系統。成功時會看到與前一篇相同的HelloFX視窗;關閉視窗後,程式會回到終端機。這也確認ASDF已經依序載入各個檔案,並完成JavaFX應用程式的建立。

https://ithelp.ithome.com.tw/upload/images/20260924/20112470Fj7NyO8cNR.png

下一篇將在這個ASDF系統的基礎上,介紹ABCL與Java的交互方式、JSS Package,以及如何使用JavaFX建立場景。


上一篇
lesson 1: Hello abclFX:參考HelloFX的簡單示例
下一篇
lesson 3: JavaFX場景和初見JSS Package:怎麼存取JAVA類別資源?
系列文
玩轉ABCL Lisp!用奇怪偏門的方式學習JavaFX 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言